/*
** Example name : gol_mpi_socket.c
**
** Network Socket is added...
**
** golc_serial	: MPI Client
**
** Version	: 1.39
**
** Updated by 2011. 09. 28
**
*/

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <math.h>
#include <mpi.h>

#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <signal.h>

#define NX		(20)		// array x size
#define NY		(20)		// array y size

int	nSteps = 100;
int	nSleepSec = 1;

#define	PORT		(20111)		// tcp/ip socket port
#define	BUF_SIZE	10240

#define	ROOT		0		// nrank = 0

// function prototype
void	error_handling	(char *message);
void	showGridState	(int **buf);
void	alloc2dArray	(int ***array, int nRow, int nColumn);
void	free2dArray	(int ***array, int n);
void	getRandomValue	(int **gol_buf);
void	calEvolution	(int **gol_buf, int nMyStartCol, int nMyEndCol);
int	getNeighborCount(int i, int j, int **tmp);
int	getGridXSize	(void);
int	getGridYSize	(void);
void	setNSteps	(int steps);
int 	getNSteps	(void);
int	getSleepSec	(void);
void	setSleepSec	(int sec);
void	writeIntMsg	(int sc, char *key, int value);
void	writeStrMsg	(int sc, char *key, char *value);
void 	strSplit	(char *str, char *delim, char buf[2][100]);

//
// start of main
//
int main(int argc, char *argv[])
{
	int	nRank, nProcs;
	int	serv_sock;
	//int	clnt_addr_size;
	struct	sockaddr_in	serv_addr;
	char	sMsg[BUF_SIZE] = "";
	int	i, j, k, l, isum;
	int	**gol_buf, **tmp_buf;
	int	nPerProc, nLeftOver;
	int	nMyStartCol, nMyEndCol;
	int	tmpStartCol, tmpEndCol;
	int	nReadAmount;
	char	sReadBuf[100];
	char	sSleepBuf[2][100];

	MPI_Status	status;

#if defined (_MPI_VIRTUAL_TOPOLOGY)
	int	nCartDims, nCartReorder, nCartRank;
	int	nDimSize[2], nPeriods[2];
#endif

#if defined (_MPI_DERIVED_DATA)
	MPI_Datatype	column_new_type;
#endif

	MPI_Init(&argc, &argv);
	MPI_Comm_rank(MPI_COMM_WORLD, &nRank);
	MPI_Comm_size(MPI_COMM_WORLD, &nProcs);

	//if (nRank == ROOT) {
	if (nProcs > (getGridXSize()/3)) {
		printf("> Usage: \n");
		printf(">        The # of processor must be less than %d (gridx:%d / cell:3).\n", getGridXSize()/3+1, getGridXSize());
		MPI_Finalize();
		exit(0);
	}
	//}

	if (nRank == ROOT && argc != 3) {
		printf("> Usage:\n");
		printf(">       ./gol_mpi_socket [seconds] [steps]\n\n");
		MPI_Finalize();
		exit(0);
	}

#if defined (_MPI_VIRTUAL_TOPOLOGY)
	if (nProcs == 1)	nCartDims = 1;
	else			nCartDims = 2;
#endif

#ifdef _DEBUG
	if (nRank == ROOT) {
		printf("debug> nRank(%d) : nProcs = %d\n", nRank, nProcs);
		printf("debug> nRank(%d) : nRank = %d\n", nRank, nRank);
	}
#endif
	alloc2dArray(&gol_buf, getGridXSize(), getGridYSize());

	if (nRank == ROOT)
	{
		getRandomValue(gol_buf);
#ifdef	_CONSOLE_VIEW
		showGridState(gol_buf);
#endif
		setSleepSec(atoi(argv[1]));		// set sleep sec
		setNSteps(atoi(argv[2]));		// set nsteps

		if ((serv_sock = socket(PF_INET, SOCK_STREAM, 0)) == -1)
			error_handling("msg> socket() creation error.");
		else
			printf("msg> socket() successfully created.\n");

		memset(&serv_addr, 0, sizeof(serv_addr));
		serv_addr.sin_family = AF_INET;
		//	serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
		//	serv_addr.sin_addr.s_addr = inet_addr("210.119.45.105");	// cannot connect
		serv_addr.sin_addr.s_addr = inet_addr("192.168.11.24");		// s0003
		serv_addr.sin_port = htons(PORT);

		if (connect(serv_sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == -1)
			error_handling("msg> connect() error.");
		else
			printf("msg> connect() successfully doned.\n");

		writeIntMsg(serv_sock, "sleep_sec", getSleepSec());
		printf("write> sleep_sec = %d\n", getSleepSec());

		writeIntMsg(serv_sock, "nsteps", getNSteps());
		printf("write> nsteps = %d\n", getNSteps());

		writeIntMsg(serv_sock, "grid_x", getGridXSize());
		printf("write> grid_x = %d\n", getGridXSize());

		writeIntMsg(serv_sock, "grid_y", getGridYSize());
		printf("write> grid_y = %d\n", getGridYSize());

#if _DEBUG
		printf("debug> NSTEPS : %d\n", getNSteps());
		printf("debug> NX : %d\n", getGridXSize());
		printf("debug> NJ : %d\n\n", getGridYSize());
		printf("debug> Wait a moment to finish the calculation...\n");
#endif

		nPerProc = (int)floor(getGridXSize() / nProcs);
		nLeftOver = getGridXSize() % nProcs;
	} // end of nRank = ROOT

#ifdef _DEBUG
	printf("debug> before> nrank (%d) : nSteps = %d, nSleepSec = %d, nPerProc = %d, nLeftOver = %d\n", nRank, nSteps, nSleepSec, nPerProc, nLeftOver);
#endif

#ifdef _MPI_P2P
	if (nRank == ROOT) {
		printf("msg> ### #ifdef _MPI_P2P ###\n\n");

		for (i=1; i<nProcs; i++) {
			MPI_Send(&nSteps, 1, MPI_INT, i, 10, MPI_COMM_WORLD);
			MPI_Send(&nSleepSec, 1, MPI_INT, i, 11, MPI_COMM_WORLD);
			MPI_Send(&nPerProc, 1, MPI_INT, i, 12, MPI_COMM_WORLD);
			MPI_Send(&nLeftOver, 1, MPI_INT, i, 13, MPI_COMM_WORLD);
		}
	}
	else {
		MPI_Recv(&nSteps, 1, MPI_INT, ROOT, 10, MPI_COMM_WORLD, &status);
		MPI_Recv(&nSleepSec, 1, MPI_INT, ROOT, 11, MPI_COMM_WORLD, &status);
		MPI_Recv(&nPerProc, 1, MPI_INT, ROOT, 12, MPI_COMM_WORLD, &status);
		MPI_Recv(&nLeftOver, 1, MPI_INT, ROOT, 13, MPI_COMM_WORLD, &status);
	}

#elif defined (_MPI_BCAST) || defined (_MPI_DERIVED_DATA)
	if (nRank == ROOT) {
		printf("msg> ### #ifdef (_MPI_BCAST) || defined (_MPI_DERIVED_DATA) ###\n\n");
	}

	MPI_Bcast(&nSteps, 1, MPI_INT, ROOT, MPI_COMM_WORLD);
	MPI_Bcast(&nSleepSec, 1, MPI_INT, ROOT, MPI_COMM_WORLD);
	MPI_Bcast(&nPerProc, 1, MPI_INT, ROOT, MPI_COMM_WORLD);
	MPI_Bcast(&nLeftOver, 1, MPI_INT, ROOT, MPI_COMM_WORLD);
#endif

#ifdef _DEBUG
	printf("debug> before> nrank (%d) : nSteps = %d, nSleepSec = %d, nPerProc = %d, nLeftOver = %d\n", nRank, nSteps, nSleepSec, nPerProc, nLeftOver);
#endif

	// Let each processor decide what column(s) to run
	nMyStartCol = nRank * nPerProc;
	nMyEndCol = nMyStartCol + nPerProc - 1;

	if (nRank == (nProcs-1))
		nMyEndCol += nLeftOver;

#ifdef _DEBUG
	printf("debug> nrank (%d) : nMyStartCol = %d, nMyEndCol = %d\n", nRank, nMyStartCol, nMyEndCol);
#endif

	// allocate memory for array holding temp values
	if (nRank != ROOT)
		alloc2dArray(&tmp_buf, (nMyEndCol-nMyStartCol+1), getGridYSize());

	// Main Loop : time steps
	for (i=0; i<getNSteps(); i++) {
		MPI_Barrier(MPI_COMM_WORLD);

		if (nProcs > 1) {
#ifdef _MPI_P2P
			if (nRank == ROOT) {
				printf("msg> ### #ifdef _MPI_P2P ###\n\n");

				for (j=1; j<nProcs; j++)
					for (k=0; k<getGridXSize(); k++)
						MPI_Send(gol_buf[k], getGridYSize(), MPI_INT, j, 10, MPI_COMM_WORLD);
			}
			else {
				for (j=0; j<getGridXSize(); j++)
					MPI_Recv(gol_buf[j], getGridYSize(), MPI_INT, ROOT, 10, MPI_COMM_WORLD, &status);
			}

#elif defined (_MPI_BCAST)
			if (nRank == ROOT)	printf("msg> ### #ifdef _MPI_BCAST ###\n\n");

			for (j=0; j<getGridXSize(); j++)
				MPI_Bcast(gol_buf[j], getGridYSize(), MPI_INT, ROOT, MPI_COMM_WORLD);

#elif defined (_MPI_DERIVED_DATA)
			if (nRank == ROOT)	printf("msg> ### #ifdef _MPI_DERIVED_DATA ###\n\n");

			MPI_Type_contiguous(getGridYSize(), MPI_INT, &column_new_type);
			MPI_Type_commit(&column_new_type);

			if (nRank == ROOT) {
				for (j=1; j<nProcs; j++)
					for (k=0; k<getGridXSize(); k++)
						MPI_Send(gol_buf[k], 1, column_new_type, j, 10, MPI_COMM_WORLD);
			}
			else {
				for (j=0; j<getGridXSize(); j++)
					MPI_Recv(gol_buf[j], 1, column_new_type, ROOT, 10, MPI_COMM_WORLD, &status);
			}
#endif
		}

#ifdef _DEBUG
		printf("debug> nrank(%d) : nMyStartCol = %d, nMyEndCol=%d\n", nRank, nMyStartCol, nMyEndCol);
#endif
		calEvolution(gol_buf, nMyStartCol, nMyEndCol);

		if (nRank != ROOT) {
#ifdef _DEBUG
			printf("debug> nrank (%d) : nMyStartCol = %d\n", nRank, nMyStartCol);
			printf("debug> nrank (%d) : nMyEndCol = %d\n", nRank, nMyEndCol);
#endif
			for (j=nMyStartCol; j<=nMyEndCol; j++)
				for (k=0; k<getGridYSize(); k++)
					tmp_buf[j-nMyStartCol][k] = gol_buf[j][k];

#if defined (_MPI_P2P) || (_MPI_BCAST)
			if (nRank == ROOT)	printf("msg> ### #if defined (_MPI_P2P) || (_MPI_BCAST) ###\n\n");
			for (j=nMyStartCol; j<=nMyEndCol; j++)
				MPI_Send(tmp_buf[j-nMyStartCol], getGridYSize(), MPI_INT, ROOT, 0, MPI_COMM_WORLD);
#elif defined (_MPI_DERIVED_DATA)
			if (nRank == ROOT)	printf("msg> ### #if defined (_MPI_DERIVED_DATA) ###\n\n");
			for (j=nMyStartCol; j<=nMyEndCol; j++)
				MPI_Send(tmp_buf[j-nMyStartCol], 1, column_new_type, ROOT, 0, MPI_COMM_WORLD);
#endif
		}
		else if (nRank == ROOT && nProcs > 1) {
			for (j=1; j<nProcs; j++) {
				tmpStartCol = j * nPerProc;
				tmpEndCol = tmpStartCol + nPerProc -1;
#ifdef _DEBUG
				printf("debug> nrank (%d) : tmpStartCol = %d\n", nRank, tmpStartCol);
				printf("debug> nrank (%d) : tmpEndCol = %d\n", nRank, tmpEndCol);
#endif
				if (j == (nProcs-1))
					tmpEndCol += nLeftOver;

				alloc2dArray(&tmp_buf, (tmpEndCol-tmpStartCol+1), getGridYSize());

#if defined (_MPI_P2P) || (_MPI_BCAST)
				if (j == 1)
					printf("msg> ### #if defined (_MPI_P2P) || (_MPI_BCAST) ###\n\n");
				for (k=0; k<(tmpEndCol-tmpStartCol+1); k++)
					MPI_Recv(tmp_buf[k], getGridYSize(), MPI_INT, j, 0, MPI_COMM_WORLD, &status);
#elif defined (_MPI_DERIVED_DATA)
				if (j == 1)
					printf("msg> ### #if defined (_MPI_DERIVED_DATA) ###\n\n");
				for (k=0; k<(tmpEndCol-tmpStartCol+1); k++)
					MPI_Recv(tmp_buf[k], 1, column_new_type, j, 0, MPI_COMM_WORLD, &status);
#endif

				for (k=tmpStartCol; k<=tmpEndCol; k++)
					for (l=0; l<getGridYSize(); l++)
						gol_buf[k][l] = tmp_buf[k-tmpStartCol][l];

				free2dArray(&tmp_buf, (tmpEndCol-tmpStartCol+1));
			}
		}

		for (j=0; j<getGridXSize(); j++) {
			for (k=0; k<getGridYSize(); k++) {
				sMsg[j*getGridXSize()+k] = gol_buf[j][k] + 48;	// convert int to ascii character
			}
		}
#if _DEBUG
	printf("debug> sMsg = %s, msg_len = %d\n", sMsg, (int)strlen(sMsg));
#endif
		if (nRank == 0) {
			writeStrMsg(serv_sock, "grid_data", sMsg);
			printf("write> grid_data = %s\n\n", sMsg);

			printf("msg> Nstep (%4d / %d) : Wait a %d seconds...\n\n", i+1, getNSteps(), getSleepSec());
			//printf("msg> read() waits...\n");

			if ((nReadAmount = read(serv_sock, sReadBuf, 100)) > 0) {
				printf("read> sReadBuf = %s\n", sReadBuf);
				//printf("read> nReadAmount = %d\n", nReadAmount);
			}

			strSplit(sReadBuf, " ", sSleepBuf);
			setSleepSec(atoi(sSleepBuf[1]));
#ifdef _DEBUG
			printf("debug> setSleepSec(%d) = %d\n", getSleepSec(), getSleepSec());
#endif
			// Iterations are done; sum the number of live cells
			for (j=0, isum=0; j<getGridXSize(); j++)
				for (k=0; k<getGridYSize(); k++)
					isum += gol_buf[j][k];

			writeIntMsg(serv_sock, "live_cells", isum);
			printf("write> live_cells = %d\n", isum);
			printf("msg> Number of live cells = %d / %d\n", isum, getGridXSize()*getGridYSize());

			printf("msg> setSleepSec(%d) = %d\n", getSleepSec(), getSleepSec());

#ifdef _CONSOLE_VIEW
		if (nRank == ROOT)
			showGridState(gol_buf);
#endif
			sleep(getSleepSec());
		}
		//MPI_Barrier(MPI_COMM_WORLD);
	} // end of NStep

	if (nRank == ROOT) {
		// Iterations are done; sum the number of live cells
		for (i=0, isum=0; i<getGridXSize(); i++) {
			for (j=0; j<getGridYSize(); j++)
				isum = isum + gol_buf[i][j];
		}
		printf("msg> Number of live cells = %d\n\n", isum);

		close(serv_sock);
	}
	else
		free2dArray(&tmp_buf, (nMyEndCol-nMyStartCol+1));

	free2dArray(&gol_buf, getGridXSize());

#if defined (_MPI_DERIVED_DATA)
	MPI_Type_free(&column_new_type);
#endif

	MPI_Finalize();

	return 0;
}

void error_handling(char *message)
{
	printf("debug> %s\n\n", message);
	MPI_Finalize();
	exit(1);
}

void showGridState(int **buf)
{
	int i, j;

	printf("\n   ");
	for (i=0; i<getGridYSize(); i++) {
		printf("%2d ", i+1);
	}

	printf("\n");
	for (i=0; i<=getGridYSize(); i++)
		printf(" --");

	printf("\n");

	for (i=0; i<getGridXSize(); i++) {
		printf("%2d|", i+1);
		for (j=0; j<getGridYSize(); j++) {
			if (buf[i][j] == 0)	printf(" - ");
			else			printf(" # ");
		}
		printf("\n");
	}
	for (i=0; i<=getGridYSize(); i++)
		printf(" --");

	printf("\n\n");
}

void alloc2dArray(int ***array, int nRow, int nColumn)
{
	int	i;

	if ((*array = (int **)malloc(nRow*sizeof(int *))) == NULL)
		error_handling("allocArray() error");

	for (i=0; i<nRow; i++) {
		if (((*array)[i] = (int *)malloc(nColumn*sizeof(int))) == NULL)
			error_handling("allocArray() error");
	}
}

void free2dArray(int ***array, int n)
{
	int	i;

	for (i=0; i<n; i++) free((*array)[i]);
	free(*array);
}

void getRandomValue(int **gol_buf)
{
	int	i, j;

	srand(time(NULL));

	for (i=0; i<getGridXSize(); i++) {
		for (j=0; j<getGridYSize(); j++) {
			gol_buf[i][j] = rand() % 2;
		}
	}
}

void calEvolution(int **gol_buf, int nMyStartCol, int nMyEndCol)
{
	int	i, j;
	int	**tmp_buf;
	int	nNeighbors;

	alloc2dArray(&tmp_buf, getGridXSize(), getGridYSize());

	// backup gol_buf
	for (i=0; i<getGridXSize(); i++)
		for (j=0; j<getGridYSize(); j++)
			tmp_buf[i][j] = gol_buf[i][j];

	for (i=nMyStartCol; i<=nMyEndCol; i++) {
		for(j=0; j<getGridYSize(); j++) {
			nNeighbors = getNeighborCount(j, i, tmp_buf);

			switch (nNeighbors) {
				//case 2: gol_buf[i][j] = gol_buf[i][j];// -> no change
				case 2: 				// -> no change
					break;
				case 3:	gol_buf[i][j] = 1;		// -> alive
					break;
				default:gol_buf[i][j] = 0;		// -> dead
					break;
			}
		}
	}

	free2dArray(&tmp_buf, getGridXSize());
}

int getNeighborCount(int i, int j, int **tmp)
{
	int	a, b, c, d, e, f, x, count = 0;

	a = j - 1;
	b = j;
	c = j + 1;
	d = i - 1;
	e = i;
	f = i + 1;

	for (x=0; x<8; x++) {
		switch (x) {
			case 0:
				if (a >= 0 && d >= 0)	count += tmp[a][d];
				break;
			case 1:
				if (a >= 0)		count += tmp[a][e];
				break;
			case 2:
				if (a >= 0 && f < getGridYSize())	count += tmp[a][f];
				break;
			case 3:
				if (f < getGridYSize())	count += tmp[b][f];
				break;
			case 4:
				if (c < getGridXSize() && f < getGridYSize())	count += tmp[c][f];
				break;
			case 5:
				if (c < getGridXSize())	count += tmp[c][e];
				break;
			case 6:
				if (c < getGridXSize() && d >= 0)	count += tmp[c][d];
				break;
			case 7:
				if (d >= 0)		count += tmp[b][d];
				break;
		}
	}

	return count;
}

int getGridXSize(void)
{
	return	(int)NX;
}

int getGridYSize(void)
{
	return (int)NY;
}

void setNSteps(int steps)
{
	nSteps = steps;
}

int getNSteps(void)
{
	return	nSteps;
}

int getSleepSec(void)
{
	return	nSleepSec;
}

void setSleepSec(int sec)
{
	nSleepSec = sec;
}

void writeIntMsg(int sc, char *key, int value)
{
	char	sMsg[BUF_SIZE];

	sprintf(sMsg, "%s %d\n", key, value);

#if _DEBUG
	printf("debug> sMsg = %s (strlen(sMsg) = %d)\n", sMsg, (int)strlen(sMsg));
#endif
	write(sc, sMsg, strlen(sMsg));
}

void writeStrMsg(int sc, char *key, char *value)
{
	char	sMsg[BUF_SIZE];

	sprintf(sMsg, "%s %s\n", key, value);

#if _DEBUG
	printf("debug> sMsg = %s (strlen(sMsg) = %d)\n", sMsg, (int)strlen(sMsg));
#endif
	write(sc, sMsg, strlen(sMsg));
}

void strSplit(char *str, char *delim, char buf[2][100])
{
	char	*p;
	int	i = 0;

	p = strtok(str, delim);
	strcpy(buf[i++], p);
#if _DEBUG
	printf("debug> p = %s\n", p);
#endif

	while (p) {
		p = strtok(NULL, delim);

		if (p) {
#if _DEBUG
			printf("debug> p = %s\n", p);
#endif
			strcpy(buf[i++], p);
		}
	}
}
